#!/bin/sh -X # no runnable script, just for editors
# set -eu is assumed.

# Under set -x, don't mess the trace with other commands' output (possibly
# redirected), rather send it directly to the global stderr.
exec 5>&2
BASH_XTRACEFD=5

# the mode common for our test scripts
set -o pipefail

EXIT_CODE=0

# During the most lifetime of the script (until the exiting traps), this var is
# just used to count the failed tests. Better do this via a dedicated function:
XFAILED=0
one_more_failure() {
	if [ "$APT_TEST_XFAIL" = '' ]; then
		EXIT_CODE=$((EXIT_CODE+1))
	else
		XFAILED=$((XFAILED+1))
	fi
}

# we all like colorful messages
if [ "$MSGCOLOR" != 'NO' ]; then
	if [ ! -t 1 ]; then # but check that we output to a terminal
		export MSGCOLOR='NO'
	fi
fi

CERROR=
CWARNING=
CMSG=
CINFO=
CDEBUG=
CNORMAL=
CDONE=
CPASS=
CFAIL=
CCMD=

if [ "$MSGCOLOR" != 'NO' ]; then
	CERROR="\033[1;31m" # red
	CWARNING="\033[1;33m" # yellow
	CMSG="\033[1;32m" # green
	CINFO="\033[1;96m" # light blue
	CDEBUG="\033[1;94m" # blue
	CNORMAL="\033[0;39m" # default system console color
	CDONE="\033[1;32m" # green
	CPASS="\033[1;32m" # green
	CFAIL="\033[1;31m" # red
	CCMD="\033[1;35m" # pink
fi

# Our messages shouldn't be messed up with commands' stdout and stderr,
# therefore we use a special FD for them.
exec 6>&2

msgdie() { printf "${CERROR}E: $1${CNORMAL}\n" >&6; exit 1; }
msgwarn() { printf "${CWARNING}W: $1${CNORMAL}\n" >&6; }
msgmsg() { printf "${CMSG}$1${CNORMAL}\n" >&6; }
msginfo() { printf "${CINFO}I: $1${CNORMAL}\n" >&6; }
msgdebug() { printf "${CDEBUG}D: $1${CNORMAL}\n" >&6; }
msgdone() { printf "${CDONE}DONE${CNORMAL}\n" >&6; }
msgnwarn() { printf "${CWARNING}W: $1${CNORMAL}" >&6; }
msgnmsg() { printf "${CMSG}$1${CNORMAL}" >&6; }
msgninfo() { printf "${CINFO}I: $1${CNORMAL}" >&6; }
msgndebug() { printf "${CDEBUG}D: $1${CNORMAL}" >&6; }
msgtest() {
	while [ $# -gt 0 ]; do
		printf "${CINFO}%s${CCMD} %s${CNORMAL} " "$1" "$(sed -e 's#^apt\([cgfs]\)#apt-\1#' <<<"${2-}")" >&6
		shift
		if [ $# -gt 0 ]; then
			shift
		else
			break
		fi
	done
	printf "${CINFO}…${CNORMAL} " >&6
}
msgpass() { printf "${CPASS}PASS${CNORMAL}\n" >&6; }
msgskip() { printf "${CWARNING}SKIP${CNORMAL}\n" >&6; }
msgfail() {
	if [ $# -gt 0 ]; then printf "${CFAIL}FAIL: $*${CNORMAL}\n" >&6;
	else printf "${CFAIL}FAIL${CNORMAL}\n" >&6; fi
	one_more_failure
}

# enable / disable Debugging
if [ $MSGLEVEL -le 0 ]; then
	# Although this override never happens with nonnegative MSGLEVEL, let's not
	# change the semantics: this msg*() function doesn't just print a message.
	msgdie() { exit 1; }
fi
if [ $MSGLEVEL -le 1 ]; then
	msgwarn() { true; }
	msgnwarn() { true; }
fi
if [ $MSGLEVEL -le 2 ]; then
	msgmsg() { true; }
	msgnmsg() { true; }
	msgtest() { true; }
	msgpass() { printf " ${CPASS}P${CNORMAL}" >&6; }
	msgskip() { printf " ${CWARNING}S${CNORMAL}" >&6; }
	if [ -n "$CFAIL" ]; then
		msgfail() { printf " ${CFAIL}FAIL${CNORMAL}" >&6; one_more_failure; }
	else
		msgfail() { printf ' ###FAILED###' >&6; one_more_failure; }
	fi
fi
if [ $MSGLEVEL -le 3 ]; then
	msginfo() { true; }
	msgninfo() { true; }
fi
if [ $MSGLEVEL -le 4 ]; then
	msgdebug() { true; }
	msgndebug() { true; }
fi
msgdone() {
	if [ "$1" = 'debug' -a $MSGLEVEL -le 4 ] ||
	   [ "$1" = 'info' -a $MSGLEVEL -le 3 ] ||
	   [ "$1" = 'msg' -a $MSGLEVEL -le 2 ] ||
	   [ "$1" = 'warn' -a $MSGLEVEL -le 1 ] ||
	   [ "$1" = 'die' -a $MSGLEVEL -le 0 ]; then
		true;
	else
		printf "${CDONE}DONE${CNORMAL}\n" >&6;
	fi
}
getaptconfig() {
	if [ -f ./aptconfig.conf ]; then
            echo './aptconfig.conf'
	elif [ -f ../aptconfig.conf ]; then
            echo '../aptconfig.conf'
        fi
}
runapt() {
	msgdebug "CWD: $PWD"
	msgdebug "Executing: APT_CONFIG=$(getaptconfig) ${CCMD}$*${CDEBUG} "
	local CMD="$1"
	shift
	APT_CONFIG="$(getaptconfig)" $CMD "$@"
}
aptconfig() { runapt apt-config "$@"; }
aptcache() { runapt apt-cache "$@"; }
aptget() { runapt apt-get "${CDROM_OPTS[@]}" "$@"; }
aptmark() { runapt apt-mark "$@"; }


exitwithstatus() {
		if [ $EXIT_CODE -gt 0 ]; then
			exit $((EXIT_CODE <= 253 ? EXIT_CODE : 253));
		elif [ $XFAILED -gt 0 ]; then
			exit 255
		elif [ -n "$APT_TEST_XFAIL" ]; then
			exit 254 # XPASS: uneXpected PASS
		fi
}

shellsetedetector() {
	local exit_status=$?
	if [ "$exit_status" != '0' ]; then
		printf >&6 "${CERROR}E: Looks like the testcases ended prematurely with exitcode: ${exit_status}${CNORMAL}\n"
		if [ "$EXIT_CODE" = '0' ]; then
			EXIT_CODE="$exit_status"
		fi
	fi
}

CURRENTTRAP=
addtrap() {
	if [ "$1" = 'prefix' ]; then
		shift
		local -r fmt="$1"; shift
		printf -v CURRENTTRAP "$fmt %s" "$@" "$CURRENTTRAP"
	else
		local -r fmt="$1"; shift
		printf -v CURRENTTRAP "%s $fmt" "$CURRENTTRAP" "$@"
	fi
	trap "shellsetedetector; $CURRENTTRAP exitwithstatus; exit 0;" 0 HUP INT QUIT ILL ABRT FPE SEGV PIPE TERM
}

# Source a script whose effect is a prerequisite.
# Do not continue, if that script has failed or x-failed.
prereq() {
	. "$1"
	if [ $EXIT_CODE -gt 0 ] || [ $XFAILED -gt 0 ]; then
	    exit
	fi
}

setupenvironment() {
	TESTDIRECTORY=$(readlink -f $(dirname $0))
	addtrap '' # unconditionally set up our basic traps

	if [ -n "$APT_TEST_WORKINGDIRECTORY" ]; then
		TMPWORKINGDIRECTORY="$APT_TEST_WORKINGDIRECTORY"
	else
		TMPWORKINGDIRECTORY=$(mktemp -d)
		addtrap 'cd /; rm -rf %q;' "$TMPWORKINGDIRECTORY"
	fi

	msgninfo "Preparing environment for ${CCMD}$(basename $0)${CINFO} in ${TMPWORKINGDIRECTORY}… "

	cd $TMPWORKINGDIRECTORY
	mkdir rootdir aptarchive keys
	cd rootdir
	mkdir -p \
		  etc/apt/apt.conf.d etc/apt/sources.list.d \
		  etc/apt/trusted.gpg.d etc/apt/preferences.d
	mkdir -p var/cache var/lib/apt var/log tmp
	mkdir -p var/lib/rpm
	# global var to be conveniently re-used in tests
	readonly APT_LISTS_DIR="$PWD/var/lib/apt/lists"
	mkdir -p "$APT_LISTS_DIR"/partial var/cache/apt/archives/partial
	cd ..

	{
		echo "Dir \"${TMPWORKINGDIRECTORY}/rootdir\";"
		echo 'Debug::NoLocking "true";'
		echo "Dir::Etc::sourcelist \"${TMPWORKINGDIRECTORY}/rootdir/etc/apt/sources.list\";"
		echo 'RPM::Options { "--justdb"; };'

		if [ -n "$METHODSDIR" ] ; then
			echo "Dir::Bin::methods \"$METHODSDIR\";"
		fi
	} > aptconfig.conf

	cat > rootdir/etc/apt/pkgpriorities << END
Important:
  basesystem
Required:
  apt
  systemd-sysvinit
  sysvinit
  openssh-server
Standard:
  postfix
END

	# just an empty sources.list for the beginning
	echo > rootdir/etc/apt/sources.list

	# cleanup the environment a bit
	export PATH="${PATH}:/usr/local/sbin:/usr/sbin:/sbin"
	export LC_ALL=C.UTF-8
	unset LANGUAGE APT_CONFIG
	unset GREP_OPTIONS

	# The configuration for rpm (install)
	mkdir -p $TMPWORKINGDIRECTORY/home_rooter
	export HOME=$TMPWORKINGDIRECTORY/home_rooter
	echo >"$HOME"/.rpmmacros
	# Not using the common tmppath helps to avoid conflicts
	# (among other things: a race in rpmioMkpath)
	echo "%_tmppath $TMPWORKINGDIRECTORY/rootdir/tmp" >>"$HOME"/.rpmmacros
	echo "%_dbpath $TMPWORKINGDIRECTORY/rootdir/var/lib/rpm" >>"$HOME"/.rpmmacros

	# Initialize rpmdb
	rpmdb --initdb

	# Include a special kind of pkg in the db to see how APT processes it
	if [ -n "$APT_TEST_GPGPUBKEY" ]; then
		rpm --import "$APT_TEST_GPGPUBKEY"
	fi

	# setup lua stuff
	mkdir -p $TMPWORKINGDIRECTORY/lua/scripts
	mkdir -p $TMPWORKINGDIRECTORY/lua/results
	echo "Dir::Bin::scripts \"$TMPWORKINGDIRECTORY/lua/scripts\";" > rootdir/etc/apt/apt.conf.d/90lua.conf

	# setup bash scripts
	mkdir -p $TMPWORKINGDIRECTORY/bash/scripts
	mkdir -p $TMPWORKINGDIRECTORY/bash/results

	# for rpmbuild
	mkdir -p $TMPWORKINGDIRECTORY/tmp

	# emulate CDROM (without affecting the config)
	MOCKUP_CDROM_STORAGE="$TMPWORKINGDIRECTORY/usr/src/RPM/DISK"
	CDROM_OPTS=(-o Acquire::CDROM::Mount="$MOCKUP_CDROM_STORAGE"
				-o Acquire::CDROM::"$MOCKUP_CDROM_STORAGE/"::Mount=/bin/true
				-o Acquire::CDROM::"$MOCKUP_CDROM_STORAGE/"::UMount=/bin/true
				# this is needed not to statvfs of the emulated CDROM FS:
				-o Debug::identcdrom=true
				# FIXME: a quick hack to add more debugging opts
				# for apt-get (non-cdrom-specific):
				-o Acquire::Verbose=yes
				-o Debug::pkgAcquire::Auth=yes
			   )

	msgdone 'info'
	msgdebug "$(rpm --eval '_dbpath: %_dbpath')"
	msgdebug "$(rpm --eval '_tmppath: %_tmppath')"
}

# The arch (the machine arch) as detected by rpm or apt
# (and as expected to appear in the APT conf by default).
#
# * `uname -m` gives the base value.
# * rpm-build has its own way to determine the arch, which refines `uname -m`.
#   See: rpmbuild --eval %_arch 2>/dev/null ||:
#   (Packages are built by default for this arch, i.e.,
#   when there is no explicit --target.)
# * rpm has its own way to determine the arch (with more incompatible refinements
#   on ARM compared to rpm-build).
# * APT has its own simplistic way to determine the arch based on `uname -m`.
#
# FIXME: APT should probably use the same value as rpm for APT::Architecture.
# FIXME: rpmbuild's and rpm's ways need to be synchronized, particularly on ARM.
if [ -z "$APT_TEST_DEFAULT_ARCH" ]; then
	APT_TEST_DEFAULT_ARCH="$(uname -m)"
fi

# The arch used for the built packages, repos, etc.
# (May be forced by $APT_TEST_TARGET together with --target.)
if [ -n "$APT_TEST_TARGET" ]; then
	# set by force
	APT_TEST_USED_ARCH="$APT_TEST_TARGET"
else
	# just set it to an appropriate value, if not set by force
	APT_TEST_USED_ARCH="$APT_TEST_DEFAULT_ARCH"
fi

buildpackage() {
	local NAME="$1"

	local target_opt=
	if [ -n "$APT_TEST_TARGET" ]; then
		target_opt=--target="$APT_TEST_TARGET"
	fi

	msgmsg "Building package: ${NAME}"
	HOME=/var/empty rpmbuild \
		--define 'EVR %{?epoch:%epoch:}%{version}-%{release}' \
		--define 'packager test@example.net' \
		--define="_tmppath $TMPWORKINGDIRECTORY/tmp" \
		--define="_topdir $TMPWORKINGDIRECTORY/usr/src/RPM" \
		$target_opt \
		-ba "${TESTDIRECTORY}/specs/${NAME}.spec" \
		1>/dev/null
}

builtpackagefile() {
	local -r NAME="$1"

	. "${TESTDIRECTORY}/specs/${NAME}.metainfo"
	# To type less in *.metainfo, assume that the spec's filename coincides with
	# the package's name by default:
	if [ -z "${name-}" ]; then
		name="$NAME"
	fi
	# If arch is not specified in the spec, we know what was given as --target.
	if [ -z "${arch-}" ]; then
		arch="$APT_TEST_USED_ARCH"
	fi

	echo "$TMPWORKINGDIRECTORY/usr/src/RPM/RPMS/${arch}/${name}-${version}-${release}.${arch}.rpm"
}

builtpackagearch() {
	local -r NAME="$1"

	. "${TESTDIRECTORY}/specs/${NAME}.metainfo"
	# If arch is not specified in the spec, we know what was given as --target.
	if [ -z "${arch-}" ]; then
		arch="$APT_TEST_USED_ARCH"
	fi

	echo "${arch}"
}

builtpackageversion() {
	local -r NAME="$1"

	. "${TESTDIRECTORY}/specs/${NAME}.metainfo"

	echo "${version}-${release}"
}

installpackage() {
	local NAME="$1"

	local PKGFILE="$(builtpackagefile "$NAME")"

	msgmsg "Installing package via rpm: $(basename "$PKGFILE")"
	rpm \
		--justdb \
		--nodeps \
		-i "$PKGFILE" \
		1>/dev/null
}

aptgetinstallpackage() {
	local NAME="$1"

	local PKGFILE="$(builtpackagefile "$NAME")"

	msgmsg "Installing package via apt-get: $(basename "$PKGFILE")"
	aptget install "$PKGFILE" \
		   1>/dev/null
}

checkdiff() {
	DIFFTEXT="$(command diff -u "$@" | sed -e '/^---/ d' -e '/^+++/ d' -e '/^@@/ d')"
	if [ -n "$DIFFTEXT" ]; then
		return 1
	else
		return 0
	fi
}

testfileequal() {
	local FILE="$1"
	shift
	msgtest 'Test for correctness of file' "$FILE"
	if [ -z "$*" ]; then
		echo -n ''
	else
		echo "$*"
	fi | {
		checkdiff $FILE - \
		    && msgpass \
		    || {
			msgfail
			echo >&6 "$DIFFTEXT"
		    }
	}

}

testempty() {
	msgtest 'Test for no output of' "$*"
	local COMPAREFILE="${TMPWORKINGDIRECTORY}/rootdir/tmp/testempty.comparefile"
	if $* >$COMPAREFILE 2>&1 && test ! -s $COMPAREFILE; then
		msgpass
	else
		msgfail
		cat >&6 $COMPAREFILE
	fi
}

testequal() {
	local MSG='Test of equality of'
	if [ "$1" = '--nomsg' ]; then
		MSG=''
		shift
	fi

	local COMPAREFILE="${TMPWORKINGDIRECTORY}/rootdir/tmp/testequal.comparefile"
	echo "$1" > $COMPAREFILE
	shift

	if [ -n "$MSG" ]; then
		msgtest "$MSG" "$*"
	fi
	"$@" 2>&1 | {
		checkdiff $COMPAREFILE - \
		    && msgpass \
		    || {
			msgfail
			echo >&6 "$DIFFTEXT"
		    }
	}
}

testscriptoutput() {
	local MSG="$1"
	shift

	msgtest 'Test of output of' "$MSG"

	testequal --nomsg "$@"
}

testscriptnooutput() {
	local MSG="$1"
	shift

	msgtest 'Test for no output of' "$MSG"

	[ ! -e "$1" ] && msgpass || msgfail
}

# Effects:
#
# * global vars OUTPUT, RESULT
#   -- so that it can be followed up by test{success,failure}
testregexmatch() {
	local MSG='Test of regex match of'
	if [ "$1" = '--nomsg' ]; then
		MSG=''
		shift
	fi

	local COMPAREMSG="$1"
	shift

	if [ -n "$MSG" ]; then
		msgtest "$MSG" "$*"
	fi

	OUTPUT="${TMPWORKINGDIRECTORY}/rootdir/tmp/testregexmatch.output"

	set +e
	"$@" &> $OUTPUT
	RESULT=$?
	set -e

	if [[ "$(cat $OUTPUT)" =~ ^$COMPAREMSG$ ]] ; then
		msgpass
	else
		msgfail
		local COMPAREFILE="${TMPWORKINGDIRECTORY}/rootdir/tmp/testregexmatch.comparefile"
		echo "$COMPAREMSG" > $COMPAREFILE
		checkdiff $COMPAREFILE $OUTPUT || true
		echo >&6 "$DIFFTEXT"
	fi
}

match_and_delete() {
	local -r PATTERN="$1"; shift
	local -r VARNAME="$1"; shift

	[[ "${!VARNAME}" =~ ^(|.*$'\n')$PATTERN(|$'\n'(.*))$ ]] &&
		printf -v "$VARNAME" '%s%s' "${BASH_REMATCH[1]}" "${BASH_REMATCH[-1]}"
}

# Effects:
#
# * global vars OUTPUT, RESULT
#   -- so that it can be followed up by test{success,failure}
testpartsexhaust() {
	local MSG='Test that regex parts exhaust the output of'
	if [ "$1" = '--nomsg' ]; then
		MSG=''
		shift
	fi

	local -a REGEXPARTS
	while [ "$1" != '--' ]; do
		REGEXPARTS=("${REGEXPARTS[@]}" "$1"); shift
		: ${1?"Missing '--' arg." 'Usage: testpartsexhaust REGEX.. -- CMD'}
	done
	# The loop would fail if there was no '--' arg.
	shift # the '--' arg

	if [ -n "$MSG" ]; then
		msgtest "$MSG" "$*"
	fi

	OUTPUT="${TMPWORKINGDIRECTORY}/rootdir/tmp/testregexmatch.output"

	set +e
	"$@" &> $OUTPUT
	RESULT=$?
	set -e
	local OUTPUT_STR="$(cat "$OUTPUT")"

	local REGEXPART
	for REGEXPART in "${REGEXPARTS[@]}"; do
		if ! match_and_delete "$REGEXPART" OUTPUT_STR; then
			msgfail
			echo "${CINFO}*** During matching parts with the output result, a part couldn't be matched.${CNORMAL}"
			echo "${CINFO}The remaining unmatched output:${CNORMAL}"
			echo "${CINFO}----------------------------------------${CNORMAL}"
			echo "$OUTPUT_STR"
			echo "${CINFO}----------------------------------------${CNORMAL}"
			echo "${CINFO}The part that didn't match:${CNORMAL}"
			echo "${CINFO}----------------------------------------${CNORMAL}"
			echo "$REGEXPART"
			return
		fi
	done
	if [ "$OUTPUT_STR" != '' ]; then
		msgfail
		echo "${CINFO}The remaining unmatched output:"
		echo "$OUTPUT_STR"
	else
		msgpass
	fi
}

skiplines() {
	local count="$1"
	shift

	"$@" 2>&1 | tail +"$count"
}

testequalor2() {
	local COMPAREFILE1="${TMPWORKINGDIRECTORY}/rootdir/tmp/testequalor2.comparefile1"
	local COMPAREFILE2="${TMPWORKINGDIRECTORY}/rootdir/tmp/testequalor2.comparefile2"
	local COMPAREAGAINST="${TMPWORKINGDIRECTORY}/rootdir/tmp/testequalor2.compareagainst"
	echo "$1" > $COMPAREFILE1
	echo "$2" > $COMPAREFILE2
	shift 2
	msgtest 'Test for equality OR of' "$*"
	$* >$COMPAREAGAINST 2>&1 || true
	if checkdiff $COMPAREFILE1 $COMPAREAGAINST >/dev/null 2>&1 || \
		checkdiff $COMPAREFILE2 $COMPAREAGAINST >/dev/null 2>&1
	then
		msgpass
	else
		msgfail
		echo "${CINFO}Diff against OR 1${CNORMAL}"
		checkdiff $COMPAREFILE1 $COMPAREAGAINST || true
		echo >&6 "$DIFFTEXT"
		echo "${CINFO}Diff against OR 2${CNORMAL}"
		checkdiff $COMPAREFILE2 $COMPAREAGAINST || true
		echo >&6 "$DIFFTEXT"
	fi
}

testshowvirtual() {
	local VIRTUAL="N: Can't select versions from package '$1' as it is purely virtual"
	local PACKAGE="$1"
	shift
	while [ -n "$1" ]; do
		VIRTUAL="${VIRTUAL}
N: Can't select versions from package '$1' as it is purely virtual"
		PACKAGE="${PACKAGE} $1"
		shift
	done
	msgtest 'Test for virtual packages' "apt-cache show $PACKAGE"
	VIRTUAL="${VIRTUAL}
N: No packages found"
	local COMPAREFILE="${TMPWORKINGDIRECTORY}/rootdir/tmp/testshowvirtual.comparefile"
	aptcache show -q=0 $PACKAGE 2>&1 | {
		checkdiff $COMPAREFILE - \
		    && msgpass \
		    || {
			msgfail
			echo >&6 "$DIFFTEXT"
		    }
	}
}

testnopackage() {
	msgtest 'Test for non-existent packages' "apt-cache show $*"
	local SHOWPKG="$(aptcache show "$@" 2>&1 | grep '^Package: ')"
	if [ -n "$SHOWPKG" ]; then
		msgfail
		echo >&6 "$SHOWPKG"
	else
		msgpass
	fi
}

testpkginstalled() {
	msgtest 'Test that package(s) are installed with' "rpm -q $*"

	testsuccess --nomsg rpm -q "$@"
}

getpackageversion() {
	local result=

	set +e
	result=$(rpm \
		 -q --qf '%{EVR}\n' "$@" \
		 2>/dev/null)
	set -e

	echo $result
}

testpkgnotinstalled() {
	msgtest 'Test that package(s) are not installed with' "rpm -q $*"

	testfailure --nomsg rpm -q "$@"
}

# If no args, don't run a command, but examine global vars OUTPUT, RESULT
# (as a follow-up test after another test).
testsuccess() {
	if [ $# -gt 0 ] && [ "$1" = '--nomsg' ]; then
		shift
	else
		if [ $# -gt 0 ]; then
			msgtest 'Test for successful execution of' "$*"
		else
			msgtest 'Test for successful execution of the last cmd'
		fi
	fi

	if [ $# -gt 0 ]; then
		OUTPUT="${TMPWORKINGDIRECTORY}/rootdir/tmp/testsuccess.output"
		set +e
		"$@" &> $OUTPUT
		RESULT=$?
		set -e
	fi

	if [ $RESULT -eq 0 ]; then
		msgpass
		if [ $MSGLEVEL -gt 3 ]; then # info
			cat >&6 $OUTPUT
		fi
	else
		msgfail
		cat >&6 $OUTPUT
	fi
}

# If no args, don't run a command, but examine global vars OUTPUT, RESULT
# (as a follow-up test after another test).
testfailure() {
	if [ $# -gt 0 ] && [ "$1" = '--nomsg' ]; then
		shift
	else
		if [ $# -gt 0 ]; then
			msgtest 'Test for failure in execution of' "$*"
		else
			msgtest 'Test for failure in execution of the last cmd'
		fi
	fi

	if [ $# -gt 0 ]; then
		OUTPUT="${TMPWORKINGDIRECTORY}/rootdir/tmp/testfailure.output"
		set +e
		"$@" &> $OUTPUT
		RESULT=$?
		set -e
	fi

	if [ $RESULT -eq 0 ]; then
		msgfail
		cat >&6 $OUTPUT
	else
		msgpass
		if [ $MSGLEVEL -gt 3 ]; then # info
			cat >&6 $OUTPUT
		fi
	fi
}

createluascript() {
	local APTNAME="$1"
	local SCRIPTFILENAME="$2"

	echo "$APTNAME:: \"${SCRIPTFILENAME}.lua\";" > rootdir/etc/apt/apt.conf.d/91lua-${SCRIPTFILENAME}.conf
	cat > $TMPWORKINGDIRECTORY/lua/scripts/${SCRIPTFILENAME}.lua << ENDSCRIPT
f = io.open("$TMPWORKINGDIRECTORY/lua/results/${SCRIPTFILENAME}.result", "a")
f:write("$APTNAME called\n")
f:close()
ENDSCRIPT
}

createbashscript() {
	local APTNAME="$1"
	local SCRIPTFILENAME="$2"

	echo "$APTNAME:: \"$TMPWORKINGDIRECTORY/bash/scripts/${SCRIPTFILENAME}.sh\";" > rootdir/etc/apt/apt.conf.d/92bash-${SCRIPTFILENAME}.conf
	cat > $TMPWORKINGDIRECTORY/bash/scripts/${SCRIPTFILENAME}.sh << ENDSCRIPT
#!/bin/bash
echo "$APTNAME called" >> "$TMPWORKINGDIRECTORY/bash/results/${SCRIPTFILENAME}.result"
ENDSCRIPT

	chmod +x $TMPWORKINGDIRECTORY/bash/scripts/${SCRIPTFILENAME}.sh
}

encode_uri_for_apt_lists() {
	local -r uri="$1"; shift

	sed -Ee 's: :%20:g; s:_:%5f:g; s:/:_:g;' <<<"$uri"
}

set_regex_quote_str() {
	local -r regex_var="$1"; shift
	local -r str="$1"; shift

	# FIXME: %q is not the correct quotation for regexes.
	if [ -n "$str" ]; then
		printf -v "$regex_var" '%q' "$str"
	else
		# workaround a problem of %q
		printf -v "$regex_var" ''
	fi
}

############################################################
# Helpers for a background apt-shell process.
# The process will be used to run commands.

# Output/effects:
# * global vars with the paths used to control the bg process (via "empty")
#   (convenient for use in other scripts):
#   BG_APTSHELL_{PIDFILE,IN,OUT,LOG}
bg_aptshell_setup() {
	mkdir -p $TMPWORKINGDIRECTORY/bg_aptshell
	BG_APTSHELL_LOG=$TMPWORKINGDIRECTORY/bg_aptshell/bg_aptshell.log
}

# Read until the prompt; consume and drop the prompt.
# The read output is output to the standard output.
bg_aptshell_read_response() {
	# "getline" actually gets the next "record" separated by RS,
	# so everything up to the prompt.
	awk -v RS='apt> ' \
	    -v ORS= \
	    'BEGIN { getline; print $0; quit; }' \
	    <&${BG_APTSHELL[0]}
}

# The FD and the PID vars associated with a coproc may start disappearing
# during exiting; so it's more convenient to bind function args (or local vars)
# to the values once. Therefore this helper function has been written.
# And it saves repeating the same code at several places.
bg_aptshell_exit_helper() {
	local -r old_input_fd="$1"; shift
	local -r old_pid="$1"; shift

	[ -z "$old_input_fd" ] || echo exit >&"$old_input_fd" ||:
	[ -z "$old_pid" ] || {
	    echo >&6 "Exiting the bg apt-shell (pid $old_pid) (ignoring its status)."
	    # Not to hang on waiting, we start a parallell subshell which
	    # will kill it after a timeout (sleep). (Or which would just sleep,
	    # and we'd wait -n on either processes, then kill it in the main
	    # script.) Idea: https://stackoverflow.com/a/22384727/94687 .
	    local killer_pid=
	    kill -0 "$old_pid" &&
		{ (sleep 5 &&
		       kill "$old_pid" &&
		       kill -0 "$old_pid" &&
		       sleep 5 &&
		       kill -KILL "$old_pid" ||: ) &
		  killer_pid=$!
		}
	    local -r killer_pid
	    wait "$old_pid" ||:
	    [ -z "$killer_pid" ] || { kill -0 "$killer_pid" && kill "$killer_pid"; } ||:
	}
}

bg_aptshell_restart() {
	bg_aptshell_exit_helper "${BG_APTSHELL[1]-}" "${BG_APTSHELL_PID-}" \
	    &>>"$BG_APTSHELL_LOG"

	# Use the runapt() wrapper for APT_CONFIG (and the debug messages).
	# (NB: runapt() should have not yet been redefined to send commands
	# to the bg apt-shell!)
        coproc BG_APTSHELL {
	    runapt apt-shell # | tee "$BG_APTSHELL_LOG"
	}

	[ -n "${BG_APTSHELL_PID-}" ] ||
		{ { echo BG_APTSHELL_LOG:; cat "$BG_APTSHELL_LOG"; echo; } >&6 ||:
		  msgdie 'the bg apt-shell failed to start normally'
		}

	addtrap 'prefix' \
		'bg_aptshell_exit_helper %q %q &>%q||:; [ "$EXIT_CODE" = 0 ] || if [ -e %q ]; then echo BG_APTSHELL_LOG:; cat %q ||:; echo; fi >&6;' \
		"${BG_APTSHELL[1]}" \
		"$BG_APTSHELL_PID" \
		"$BG_APTSHELL_LOG" \
		"$BG_APTSHELL_LOG" \
		"$BG_APTSHELL_LOG"

	bg_aptshell_read_response >/dev/null
}

runaptcmd_in_bg_apt_shell() {
	msgdebug "CWD: $PWD"
	local -r CMD="$1"; shift
	msgdebug "Executing in bg apt-shell (normally $CMD): ${CCMD}$*${CDEBUG} "
	echo "$*" >&"${BG_APTSHELL[1]}"

	# Consume the echoed command (if any) in the output. (It seems that
	# readline usually echoes the input, but sometimes maybe not.)
	local the_rest_of_the_line
	read -r the_rest_of_the_line <&"${BG_APTSHELL[0]}"
	# Sanity check of our expectations:
	[ "$the_rest_of_the_line" = "$*" ] || [ "$the_rest_of_the_line" = '' ]

	bg_aptshell_read_response
	# TODO: try to detect the status (success/error) based on the output
}

# The commands of apt-config are not available in apt-shell.
#aptconfig_in_bg_apt_shell() { runaptcmd_in_bg_apt_shell apt-config "$@"; }
aptcache_in_bg_apt_shell() { runaptcmd_in_bg_apt_shell apt-cache "$@"; }
aptget_in_bg_apt_shell() { runaptcmd_in_bg_apt_shell apt-get "$@"; }
aptmark_in_bg_apt_shell() { runaptcmd_in_bg_apt_shell apt-mark "$@"; }
